/*
 * $Id: sysapi.c,v 1.43 2006-04-03 10:25:08 bacon Exp $
 */

#include <xp/bas/sysapi.h>
#include <xp/bas/stdarg.h>
#include <xp/bas/locale.h>
#include <xp/bas/string.h>
#include <xp/bas/mstring.h>
#include <xp/bas/assert.h>

#ifdef _WIN32
	#include <io.h> /* TODO: remove dependency on this */
#else
	#include <sys/stat.h>
	#include <fcntl.h>
#endif

xp_mode_t xp_umask (xp_mode_t mode)
{
	return umask (mode);
}

xp_hnd_t xp_open (const xp_char_t* path, int flags, ...)
{
	xp_hnd_t handle;

#ifdef _WIN32

	DWORD desired_access = 0;
	DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
	DWORD creation_disposition = 0;
	DWORD attributes = FILE_ATTRIBUTE_NORMAL;
	DWORD file_type;
	
	/* TODO: xp_seterrno... */

	if (flags & XP_OPEN_READ) desired_access |= GENERIC_READ;
	if (flags & XP_OPEN_WRITE) desired_access |= GENERIC_WRITE;

	if (flags & XP_OPEN_CREATE) {
		creation_disposition = 
			(flags & XP_OPEN_EXCLUSIVE)? CREATE_NEW:
			(flags & XP_OPEN_TRUNCATE)? CREATE_ALWAYS: OPEN_ALWAYS;
	}
	else if (flags & XP_OPEN_TRUNCATE) {
		creation_disposition = TRUNCATE_EXISTING;
	}
	else creation_disposition = OPEN_EXISTING;

	/*if (flags & XP_FIO_SYNC) attributes |= FILE_FLAG_WRITE_THROUGH; */
	/* TODO: share mode */

	handle = CreateFile (path, 
		desired_access, share_mode, XP_NULL, 
		creation_disposition, attributes, 0);
	if (handle == XP_HND_INVALID) return XP_HND_INVALID;

/* TODO: implement append
	fio->flags = 0;
	if (flags & XP_OPEN_APPEND) fio->flags |= XP_OPEN_APPEND;
*/
/* TODO: implement nonblock...
	if (flags & XP_OPEN_NONBLOCK) desired_access |= O_NONBLOCK;
*/

	file_type = GetFileType(handle);
	if (file_type == FILE_TYPE_UNKNOWN) {
		CloseHandle (handle);
		return XP_HND_INVALID;
	}

	/*
	if (file_type == FILE_TYPE_CHAR) 
	else if (file_type == FILE_TYPE_PIPE)
	*/
	/* TODO: a lot more */

#else
	int desired_access = 0;
	xp_va_list arg;
	xp_mode_t mode;

	#ifdef XP_CHAR_IS_MCHAR
	const xp_mchar_t* path_mb;
	#else
	xp_mchar_t path_mb[XP_PATH_MAX + 1];
	#endif

	xp_va_start (arg, flags);
	if (xp_sizeof(xp_mode_t) < xp_sizeof(int))
		mode = xp_va_arg (arg, int);
	else
		mode = xp_va_arg (arg, xp_mode_t);
	xp_va_end (arg);

	#ifdef XP_CHAR_IS_MCHAR
	path_mb = path;
	#else
	if (xp_wcstomcs_strict (
		path, path_mb, xp_countof(path_mb)) == -1) return -1;
	#endif

	if (flags & XP_OPEN_READ) desired_access = O_RDONLY;
	if (flags & XP_OPEN_WRITE) {
		if (desired_access == 0) desired_access |= O_WRONLY;
		else desired_access = O_RDWR;
	}

	if (flags & XP_OPEN_APPEND) desired_access |= O_APPEND;
	if (flags & XP_OPEN_CREATE) desired_access |= O_CREAT;
	if (flags & XP_OPEN_TRUNCATE) desired_access |= O_TRUNC;
	if (flags & XP_OPEN_EXCLUSIVE) desired_access |= O_EXCL;
	if (flags & XP_OPEN_NONBLOCK) desired_access |= O_NONBLOCK;

	handle = open (path_mb, desired_access, mode);
#endif

	return handle;
}

int xp_close (xp_hnd_t handle)
{
#ifdef _WIN32
	if (CloseHandle(handle) == FALSE) return -1;
	return 0;
#else
	return close (handle);
#endif
}

xp_ssize_t xp_read (xp_hnd_t handle, void* buf, xp_size_t size)
{
#ifdef _WIN32

	DWORD bytes_read;
	xp_size_t n = 0;
	xp_byte_t* p = (xp_byte_t*)buf;

	while (1) {
		if (size <= XP_TYPE_MAX(DWORD)) {
			if (ReadFile(handle, p, size, 
				&bytes_read, XP_NULL) == FALSE) return -1;
			n += bytes_read;
			break;
		}

		if (ReadFile(handle, p, XP_TYPE_MAX(DWORD),
			&bytes_read, XP_NULL) == FALSE) return -1;
		if (bytes_read == 0) break; /* reached the end of a file */

		p += bytes_read;
		n += bytes_read;
		size -= bytes_read;
	}

	return (xp_ssize_t)n;

#else

	return read (handle, buf, size);

#endif
}

xp_ssize_t xp_write (xp_hnd_t handle, const void* data, xp_size_t size)
{
#ifdef _WIN32

	DWORD bytes_written;
	xp_size_t n = 0;
	const xp_byte_t* p = (const xp_byte_t*)data;

/* TODO: 
	if (fio->flags & XP_OPEN_APPEND) {
		xp_seek (handle, 0, XP_SEEK_END);
	}
*/

	while (1) {
		if (size <= XP_TYPE_MAX(DWORD)) {
			if (WriteFile(handle, p, size, 
				&bytes_written, XP_NULL) == FALSE) return -1;
			n += bytes_written;
			break;
		}

		if (WriteFile(handle, p, XP_TYPE_MAX(DWORD), 
			&bytes_written, XP_NULL) == FALSE) return -1;
		if (bytes_written == 0) break;

		p += bytes_written;
		n += bytes_written;
		size -= bytes_written;
	}

	return (xp_ssize_t)n;

#else

	return write (handle, data, size);

#endif
}

xp_off_t xp_seek (xp_hnd_t handle, xp_off_t offset, int origin)
{
#ifdef _WIN32

	static int __seek_map[] = 
	{
		FILE_BEGIN,
		FILE_CURRENT,
		FILE_END	
	};
	LARGE_INTEGER x, y;

	xp_assert (xp_sizeof(offset) <= xp_sizeof(x.QuadPart));

	x.QuadPart = offset;
	if (SetFilePointerEx (
		handle, x, &y, __seek_map[origin]) == FALSE) return -1;

	return (xp_off_t)y.QuadPart;

#else

	static int __seek_map[] = 
	{
		SEEK_SET,
		SEEK_CUR,
		SEEK_END
	};

	return lseek (handle, offset, __seek_map[origin]);

#endif
}

int xp_hstat (xp_hnd_t handle, xp_stat_t* buf)
{
#ifdef _WIN32
	// TODO:....
	return -1;
#else
	return fstat (handle, buf);
#endif
}

int xp_hchmod (xp_hnd_t handle, xp_mode_t mode)
{
#ifdef _WIN32
	/* TODO: */
	return -1;
#else
	return fchmod (handle, mode);
#endif
}

int xp_htruncate (xp_hnd_t handle, xp_off_t size)
{
#ifdef _WIN32
	#ifndef INVALID_SET_FILE_POINTER
	#define INVALID_SET_FILE_POINTER ((DWORD)-1)
	#endif

	/* TODO: support lsDistanceToMoveHigh (high 32bits of 64bit offset) */
	if (SetFilePointer(
	    	handle,0,NULL,FILE_CURRENT) == INVALID_SET_FILE_POINTER ||
	    SetFilePointer(
	    	handle,size,NULL,FILE_BEGIN) == INVALID_SET_FILE_POINTER ||
	    SetEndOfFile(handle) == FALSE) return -1;

	return 0;
#else
	return ftruncate (handle, size);
#endif
}

